#ifndef _console
#define _console

#define eol console::eol
//#define repl console::repl
#define cll console::cll

#include <vector>
#include <font.h>
#include <box.h>
#include <utils.h>
using namespace std;

class console {

  box<int> win;

  vector<string> lines;
  int nlines;
  int lines_per_screen;
  int startl;

  string last_line;
  char BUFFER [256];

  bool visible;


  void push_back (const string& line) {

    lines.push_back (line);
    ++nlines;
    last ();

  }

  void up (int i) {
    startl -= i;
    if (startl < 0) startl = 0;
  }

  void down (int i) {
    startl += i;
    if (startl >= nlines) startl = nlines - 1;
  }

  void last () {
		// ensures last line is always displayed
    end ();
    up (lines_per_screen - 2);
  }


  void add_last_line () {
		push_back (last_line);
		last_line = "";
  }

/*
  void replace_last_line () {

  	if (nlines) {
  		--nlines;
  		lines.pop_back ();
  	}

		add_last_line ();

  }
*/

  public:

		static const char eol = '\n'; // end of last line
//		static const char repl = '\r'; // replace last line
		static const char cll = '\b'; // clear last line

		console () {
			set_visible (true);
			clear ();
		}

		console& operator<< (unsigned int i) {
			sprintf (BUFFER, "%x", i);
			last_line += BUFFER;
			return *this;
		}

		console& operator<< (int i) {
			sprintf (BUFFER, "%d", i);
			last_line += BUFFER;
			return *this;
		}

		console& operator<< (const string& s) {
			sprintf (BUFFER, "%s", s.c_str());
			last_line += BUFFER;
			return *this;
		}

		console& operator<< (double d) {
			sprintf (BUFFER, "%f", d);
			last_line += BUFFER;
			return *this;
		}

		console& operator<< (char c) {

			switch (c) {

				case eol:
					add_last_line ();
					break;

				/*case repl:
					replace_last_line ();
					break;*/

				case cll:
					last_line = "";
					break;

				default:
					sprintf (BUFFER, "%c", c);
					last_line += BUFFER;
					break;
			}
			return *this;
		}

    void draw () {

			glMatrixMode (GL_MODELVIEW);
			glPushMatrix ();
			glLoadIdentity ();

			glMatrixMode (GL_PROJECTION);
			glPushMatrix ();
			glLoadIdentity ();
			glOrtho (win.left, win.right, win.bottom, win.top, 0, 1);

			setcolor (0xff, 0xe6, 0xc4);

			int cx = win.left + get_char_width (), cy = win.top - get_char_height();

			int n = 0, i = startl;
			while ((n++ < lines_per_screen) && (i < nlines)) {
				draw_string (lines[i++], cx, cy, 0);
				cy -= get_char_height ();
			}

			draw_string (last_line, cx, cy, 0);

			glPopMatrix ();

			glMatrixMode (GL_MODELVIEW);
			glPopMatrix ();


    }

    void clear () {
      lines.clear ();
      nlines = 0;
      startl = 0;
    }

    void toggle () {
      visible = !visible;
    }

    console& operator-- () {
      up (1);
      return *this;
    }

    console& operator++ () {
      down (1);
      return *this;
    }

    void pgdn () {
      down (lines_per_screen);
    }

    void pgup () {
      up (lines_per_screen);
    }

    void home () {
      startl = 0;
    }

    void end () {
      startl = nlines - 1;
    }

		void set_window (const box<int>& w) {
			win = w;
			lines_per_screen = (win.top - win.bottom) / get_char_height ();
		}

		bool is_visible () {
			return visible;
		}

		void set_visible (bool v) {
			visible = v;
		}

};
#endif
